package com.yuxl.common.auth.service;


import com.alibaba.fastjson2.JSONObject;
import com.yuxl.common.auth.config.JwtParameter;
import com.yuxl.common.core.domain.auth.JwtUser;
import com.yuxl.common.auth.domain.LoginUser;
import com.yuxl.common.auth.domain.Token;
import com.yuxl.common.auth.utils.JwtUtil;
import com.yuxl.common.core.domain.socket.ToMessage;
import com.yuxl.common.core.handler.ApiException;
import com.yuxl.common.core.utils.SocketUtil;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.UUID;

/**
 * @author yuxl
 */
@Slf4j
@Service
public class SecurityService {


    @Resource
    private AuthUserService authUserService;

    @Resource
    private ReactiveRedisTemplate<String, String> reactiveRedisTemplate;

    @Resource
    private JwtParameter jwtParameter;
    @Resource
    private JwtUtil jwtUtil;

    private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    public Mono<Token> login(LoginUser loginUser) {
        return authUserService.findByUsername(loginUser.username())
                .flatMap(user -> {
                    if (!passwordEncoder.matches(loginUser.password(), user.getPassword())) {
                        return Mono.error(new ApiException(HttpStatus.FORBIDDEN,"账号或密码错误"));
                    }
                    JwtUser jwt = new JwtUser(user.getId(), user.getUsername());
                    String accessToken = jwtUtil.generateToken(jwt);
                    String refreshToken = UUID.randomUUID().toString().replace("-", "");
                    // 存储accessToken到Redis
                    LocalDateTime createTime = LocalDateTime.now();
                    Mono<Boolean> accessMono = reactiveRedisTemplate.opsForValue()
                            .set(jwtParameter.getTokenKey() + accessToken, jwt.toString(), Duration.ofSeconds(jwtParameter.getExpirationTime()));
                    // 存储refreshToken到Redis，value为accessToken
                    Mono<Boolean> refreshMono = reactiveRedisTemplate.opsForValue()
                            .set(jwtParameter.getRefreshTokenKey() + refreshToken, jwt.toString(), Duration.ofSeconds(jwtParameter.getRefreshExpirationTime()));
                    // 发送消息
                    ToMessage<String> toMessage = new ToMessage<>("/security/login", loginUser.username());
                    Mono<Long> socketMsg = SocketUtil.sendToAll(toMessage);
                    return Mono.zip(accessMono, refreshMono, socketMsg)
                            .map((t) ->
                                    new Token(
                                            accessToken,
                                            createTime.plusSeconds(jwtParameter.getExpirationTime()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
                                            refreshToken,
                                            createTime.plusSeconds(jwtParameter.getRefreshExpirationTime()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
                                    )
                            );
                })
                .switchIfEmpty(Mono.error(new ApiException(HttpStatus.FORBIDDEN,"账号或密码错误")));
    }

    public Mono<Long> logout(String token) {
        return reactiveRedisTemplate.delete(jwtParameter.getTokenKey() + token)
                .flatMap(i -> {
                    try {
                        JwtUser jwt = jwtUtil.getJwtFromToken(token);
                        ToMessage<JwtUser> toMessage = new ToMessage<>("/security/logout", jwt);
                        return SocketUtil.sendToAll(toMessage);
                    }catch (Exception e){
                        return Mono.just(0L);
                    }
                });
    }

    /**
     * 查询当前登录用户信息
     */
    public Mono<JwtUser> getCurrentUser() {
        return jwtUtil.getCurrentUser();
    }


    public Mono<Token> refreshToken(String refreshToken) {
        // 1. 校验refreshToken有效性
        String refreshTokenKey = jwtParameter.getRefreshTokenKey() + refreshToken;
        return reactiveRedisTemplate.opsForValue().get(refreshTokenKey)
                .flatMap(jwtStr ->
                        reactiveRedisTemplate.getExpire(refreshTokenKey)
                                .flatMap(expire -> {
                                    // 2.
                                    if (expire.toSeconds() <= jwtParameter.getExpirationTime()) {
                                        return Mono.error(new ApiException("refreshToken已无法刷新"));
                                    }
                                    JwtUser jwt = JSONObject.parseObject(jwtStr, JwtUser.class);
                                    // 3. 生成新accessToken
                                    String newAccessToken = jwtUtil.generateToken(jwt);
                                    // 4. 更新Redis
                                    LocalDateTime createTime = LocalDateTime.now();
                                    log.info("刷新的token:"+newAccessToken);
                                    return reactiveRedisTemplate.opsForValue()
                                            .set(jwtParameter.getTokenKey() + newAccessToken, JSONObject.toJSONString(jwt), Duration.ofSeconds(jwtParameter.getExpirationTime()))
                                            .thenReturn(
                                                    new Token(
                                                            newAccessToken,
                                                            createTime.plusSeconds(jwtParameter.getExpirationTime()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli(),
                                                            refreshToken,
                                                            createTime.plusSeconds(expire.toSeconds()).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
                                                    )
                                            );
                                }))
                .switchIfEmpty(Mono.error(new ApiException("无效的refreshToken")));
    }
}
